/*! \file Device.cpp
 *	\brief 
 *
 *	created:	2010/04/10
 *	created:	10:4:2010   23:24
 *	filename: 	c:\src\tactics-engine\libs\gd_dx11\Device.cpp
 *	file path:	c:\src\tactics-engine\libs\gd_dx11
 *	file base:	Device
 *	file ext:	cpp
 *	author:		Vitalii Trubchaninov
 *				venngraydog@gmail.com
 */

#include "gd_dx11/stdafx.h"
#include "gd_dx11/Device.h"

#include <app_win/Window.h>
#include <core/assert.h>
#include <core/exception.h>
#include <core/debug.h>
#include <gd/DeviceContext.h>

#include "gd_dx11/DeviceContext.h"
#include "gd_dx11/Driver.h"
#include "gd_dx11/PrimitiveBuilder.h"
#include "gd_dx11/ShaderBuilder.h"
#include "gd_dx11/StringConversions.h"

#pragma comment(lib, "d3d11.lib")

namespace tt{
namespace gd_dx11{

Device::Device(Driver* driver, const SharedPtr<app_win::Window>& window)
: m_device(NULL)
, m_swapChain(NULL)
, m_deviceContext()
{
	TT_UNUSED(driver);
	ttAssert(driver);

	createDevice(window);
	createRenderTargetView();
	m_deviceContext->setViewport(window);
}

Device::~Device(void)
{
	m_rasterizerState.reset();

// 	BOOL fullScreenState = FALSE;
// 	m_swapChain->GetFullscreenState(&fullScreenState, NULL);
// 	if (fullScreenState)
// 	{
// 		m_swapChain->SetFullscreenState(FALSE, NULL);
// 	}

	m_swapChain.reset();

	m_deviceContext.reset();

	m_device.reset();
}

utils::COMPointer<ID3D11Device>& Device::d3dDevice()
{
	return m_device;
}

//////////////////////////////////////////////////////////////////////////
void Device::createDevice(const SharedPtr<app_win::Window>& window)
{
	HWND mainWnd = window->hWnd();
	if (!mainWnd)
		ttThrow(TEXT("driver->d3dDriver()->CreateDevice create failed! mainWnd == NULL"));

	const app::WindowSize& windowSize = window->bounds().size();

	UINT createDeviceFlags = 0;
#ifdef _DEBUG
	createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

	D3D_DRIVER_TYPE driverTypes[] =
	{
		D3D_DRIVER_TYPE_HARDWARE,
		D3D_DRIVER_TYPE_WARP,
		D3D_DRIVER_TYPE_REFERENCE,
	};
	const UINT numDriverTypes = _countof( driverTypes );

	D3D_FEATURE_LEVEL featureLevels[] = 
	{
		D3D_FEATURE_LEVEL_11_0,
		D3D_FEATURE_LEVEL_10_1,
		D3D_FEATURE_LEVEL_9_3,
	};
	const UINT numFeatureLevels = _countof(featureLevels);
	D3D_FEATURE_LEVEL featureLevelOut = D3D_FEATURE_LEVEL_9_1;

	DXGI_SWAP_CHAIN_DESC sd;
	ZeroMemory( &sd, sizeof( sd ) );
	sd.BufferCount = 1;
	sd.BufferDesc.Width = windowSize.x;
	sd.BufferDesc.Height = windowSize.y;
	sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	sd.BufferDesc.RefreshRate.Numerator = 60;
	sd.BufferDesc.RefreshRate.Denominator = 1;
	sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	sd.OutputWindow = mainWnd;
	sd.SampleDesc.Count = 1;
	sd.SampleDesc.Quality = 0;
	sd.Windowed = !window->fullscreen();

	utils::COMPointer<ID3D11DeviceContext> immediateContext;

	HRESULT hr = S_OK;
	D3D_DRIVER_TYPE driverType = D3D_DRIVER_TYPE_UNKNOWN;
	for( UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++ )
	{
		driverType = driverTypes[driverTypeIndex];
		HRESULT hr = D3D11CreateDeviceAndSwapChain(
			NULL, 
			driverType, 
			NULL, 
			createDeviceFlags,
			featureLevels, 
			numFeatureLevels, 
			D3D11_SDK_VERSION, 
			&sd, 
			m_swapChain.getRef(), 
			m_device.getRef(), 
			&featureLevelOut, 
			immediateContext.getRef() );
		if( SUCCEEDED( hr ) )
			break;
	}
	if( FAILED( hr ) )
		ttThrow(TEXT("D3D11CreateDeviceAndSwapChain failed!"));

	m_deviceContext.reset( new DeviceContext(immediateContext) );

	TT_OUTPUT( Format(TEXT("Driver type=%1%, feautersLevelOut=%2%")) % ToString(driverType) % ToString(featureLevelOut) );
}

void Device::createRenderTargetView()
{
	utils::COMPointer<ID3D11Texture2D> pBackBuffer(NULL);

	// Get a pointer to the back buffer
	HRESULT hr = m_swapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), ( LPVOID* )pBackBuffer.getRef() );
	if( FAILED( hr ) )
		ttThrow(TEXT("m_swapChain->GetBuffer failed!"));

	utils::COMPointer<ID3D11RenderTargetView> renderTargetView;

	// Create a render-target view
	hr = m_device->CreateRenderTargetView( pBackBuffer.get(), NULL, renderTargetView.getRef() );
	if( FAILED( hr ) )
		ttThrow(TEXT("m_d3dDevice->CreateRenderTargetView failed!"));

	m_deviceContext->setRenderTarget(renderTargetView);
}

void Device::configureRasterizerState()
{
	// Configure pipeline state
	// ========================

	// RS state
	D3D11_RASTERIZER_DESC rsDesc;
	ZeroMemory( &rsDesc, sizeof( D3D11_RASTERIZER_DESC ) );

	rsDesc.CullMode = D3D11_CULL_BACK;
	rsDesc.FillMode = D3D11_FILL_WIREFRAME;
	rsDesc.DepthClipEnable = TRUE;

	HRESULT hr = m_device->CreateRasterizerState( &rsDesc, m_rasterizerState.getRef() );
	if( FAILED( hr ) )
		ttThrow(TEXT("m_d3dDevice->CreateRenderTargetView failed!"));
}

//////////////////////////////////////////////////////////////////////////
void Device::onPresent()
{
	m_swapChain->Present(0, 0);
}

SharedPtr<gd::DeviceContext> Device::onDeviceContext()
{
	return m_deviceContext;
}

SharedPtr<gd::PrimitiveBuilder> Device::onCreatePrimitiveBuilder()
{
	SharedPtr<gd::PrimitiveBuilder> primitiveBuilder( new PrimitiveBuilder(this) );
	return primitiveBuilder;
}

SharedPtr<gd::ShaderBuilder> Device::onCreateShaderBuilder()
{
	SharedPtr<gd::ShaderBuilder> shaderBuilder( new ShaderBuilder(this) );
	return shaderBuilder;
}

} //namespace gd_dx11
} //namespace tt
